﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using CompactMapper.Orm.UnitOfWork.Mappings.Meta.PropertyInfos;
using CompactMapper.Orm.UnitOfWork.Mappings.Meta.PropertyInfos.Columns;
using CompactMapper.Orm.UnitOfWork.Mappings.Meta.PropertyInfos.Relations;
using CompactMapper.Orm.Annotations;
using CompactMapper.Orm.UnitOfWork.Mappings.Meta.PropertyInfos.Indexes;

namespace CompactMapper.Orm.UnitOfWork.Mappings.Meta.Table
{
    internal class TableMetaInfo
    {
        private readonly Boolean _leaf;
        private readonly PropertyInfoMeta[] _persistenceProperties;
        private readonly Relation[] _relationProperties;
        private readonly String[] _childTableNames;
        private readonly Column[] _columnProperties;
        private readonly IPrimaryKeyColumn _primaryKeyColumn;
        private readonly Dictionary<String, Relation> _relationsByTableName;
        private readonly Dictionary<String, RuntimeMethodHandle> _addMethodsByTableName;
        private readonly ConstructorInfo _constructorInfo;
        private readonly String _name;
        private readonly Dictionary<String, Index[]> _indexes;

        public TableMetaInfo(Type tableType)
        {
            _constructorInfo = tableType.GetConstructor(new Type[] { });
            _name = tableType.Name;
            _persistenceProperties = PropertyInfoMeta.GetPersistencePropertyInfoMetas(tableType).ToArray();
            _relationProperties = _persistenceProperties.OfType<Relation>().ToArray();
            _childTableNames = _relationProperties.Select(meta => meta.TableName).ToArray();
            _relationsByTableName = _relationProperties.ToDictionary(meta => meta.TableName);
            _columnProperties = _persistenceProperties.OfType<Column>().ToArray();
            _primaryKeyColumn = _columnProperties.OfType<IPrimaryKeyColumn>().SingleOrDefault();
            _leaf = !_relationProperties.Any();
            _addMethodsByTableName = new Dictionary<string, RuntimeMethodHandle>();
            _indexes = _persistenceProperties.OfType<Index>().GroupBy(index => index.IndexName).ToDictionary(g => g.Key, g => g.ToArray());
        }


        public object Create()
        {
            return _constructorInfo.Invoke(new object[] { });
        }

        public String Name
        {
            get { return _name; }
        }

        public Boolean Leaf
        {
            get { return _leaf; }
        }

        public IEnumerable<string> RelatedTableNames
        {
            get { return _childTableNames; }
        }

        public IEnumerable<Relation> Relations
        {
            get { return _relationProperties; }
        }

        public IDictionary<String, Relation> RelationsByTableName
        {
            get { return _relationsByTableName; }
        }

        public IColumn PrimaryKeyColumn
        {
            get { return _primaryKeyColumn; }
        }

        public IDictionary<String, Relation> RelationProperties
        {
            get { return _relationProperties.ToDictionary(p => p.TableName); }
        }

        public IEnumerable<Column> Columns
        {
            get { return _columnProperties; }
        }

        public Dictionary<String, Index[]> Indexes
        {
            get
            {
                return _indexes;
            }
        }

        private readonly object _addMethodsByTableNameLocker = new object();

        public MethodInfo GetAddMethodByTableName(String tableName)
        {
            if (!_addMethodsByTableName.ContainsKey(tableName))
            {
                lock (_addMethodsByTableNameLocker)
                {
                    if (!_addMethodsByTableName.ContainsKey(tableName))
                    {
                        _addMethodsByTableName.Add(tableName,
                                                   Create().GetType().GetMethod("Add" + tableName).MethodHandle);
                    }
                }
            }
            return MethodBase.GetMethodFromHandle(_addMethodsByTableName[tableName]) as MethodInfo;
        }
    }
}